using System;
using Heaven.Graphics;
using Heaven.Mathematics;

namespace Heaven.Engine.Animation
{
    /// <summary>
    /// This class represents a transition 
    /// between several states of a parameter i.e. key frames.
    /// In other words, this is a minimal animational item
    /// </summary>
    public class Timeline
    {
        #region Events

        /// <summary>
        /// Occurs when the timeline or one of its parts have been changed
        /// </summary>
        public event EventHandler Invalidated;

        void Invalidate()
        {
            if (Invalidated != null) Invalidated(this, EventArgs.Empty);
        }

        #endregion

        #region Fields

        /// <summary>
        /// A parameter, state of which will be modulated
        /// </summary>
        IProperty controller = null;
        /// <summary>
        /// A collection of key frames
        /// </summary>
        KeyframeCollection keyframes = new KeyframeCollection();
        // Begin time
        double beginTime = 0;

        #endregion

        #region Properties

        /// <summary>
        /// Target property
        /// </summary>
        public IProperty Target
        {
            get { return controller; }
            set 
            { 
                controller = value;
                Invalidate();
            }
        }

        /// <summary>
        /// A collection of key frames
        /// </summary>
        public KeyframeCollection Keyframes
        {
            get { return keyframes; }
            set
            {
                keyframes.Invalidated -= new EventHandler(OnKeyframesInvalidated);
                keyframes = value;
                keyframes.Invalidated += new EventHandler(OnKeyframesInvalidated);
                Invalidate();
            }
        }

        void OnKeyframesInvalidated(object sender, EventArgs e)
        {
            Invalidate();
        }

        /// <summary>
        /// Gets the duration of the animation, milliseconds
        /// </summary>
        public double Duration
        {
            get
            {
                if (keyframes.Count == 0) return 0.0;
                return keyframes.Last.Time - keyframes.First.Time;
            }
        }

        /// <summary>
        /// The time at which this Timeline should begin, in ms
        /// </summary>
        public double BeginTime
        {
            get { return beginTime; }
            set 
            { 
                beginTime = value;
                Invalidate();
            }
        }

        #endregion

        #region Initialization

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="owner">Owner of the timeline</param>
        public Timeline(IProperty owner)
        {
            this.controller = owner;
            this.keyframes.Invalidated += new EventHandler(OnKeyframesInvalidated);
        }

        #endregion

        #region Methods

        /// <summary>
        /// Sets the proper state in the controller according to the given timestamp
        /// </summary>
        /// <param name="milliseconds">Current time, in ms</param>
        public void Apply(double milliseconds)
        {
            // Consider begin time
            milliseconds -= beginTime;

            // If we haven't any keyframes we just exit
            if (keyframes.Count == 0) return;

            Keyframe[] neighbours = keyframes.GetNeighbors(milliseconds);

            // Case of no need to interpolate
            if (neighbours.Length == 1)
            {
                controller.Value = neighbours[0].State;
                return;
            }

            // Progress considering spended time [0..1]
            double progress = (neighbours[0].Time - milliseconds) / (neighbours[0].Time - neighbours[1].Time);

            object state = neighbours[0].State;

            if (state is Double)
            {
                double a = (Double)neighbours[0].State;
                double b = (Double)neighbours[1].State;
                controller.Value = a + (b - a) * progress;
            }
            else if (state is Vector3)
            {
                Vector3 a = (Vector3)neighbours[0].State;
                Vector3 b = (Vector3)neighbours[1].State;
                controller.Value = a + (b - a) * progress;
            }
            else if (state is Vector4)
            {
                Vector4 a = (Vector4)neighbours[0].State;
                Vector4 b = (Vector4)neighbours[1].State;
                controller.Value = a + (b - a) * progress;
            }
            else if (state is Color)
            {
                Color a = (Color)neighbours[0].State;
                Color b = (Color)neighbours[1].State;
                controller.Value = a + (b - a) * progress;
            }
            else if (state is Vector2)
            {
                Vector2 a = (Vector2)neighbours[0].State;
                Vector2 b = (Vector2)neighbours[1].State;
                controller.Value = a + (b - a) * progress;
            }
            else if (state is Single)
            {
                double a = (Double)(Single)neighbours[0].State;
                double b = (Double)(Single)neighbours[1].State;
                controller.Value = (Single)(a + (b - a) * progress);
            }
            else if (state is Decimal)
            {
                double a = (Double)(Decimal)neighbours[0].State;
                double b = (Double)(Decimal)neighbours[1].State;
                controller.Value = (Decimal)(a + (b - a) * progress);
            }
            else if (state is int)
            {
                double a = (Double)(int)neighbours[0].State;
                double b = (Double)(int)neighbours[1].State;
                controller.Value = (int)(a + (b - a) * progress);
            }
            else
                // Default way to apply state without interpolation
                controller.Value = neighbours[0].State;
        }
        
        #endregion
    }
}
